<?php
declare(ticks=1);

/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/3/7 0007
 * Time: 21:03
 */
class Worker2
{

    protected $socket;

    public $connect;

    public $message;

    public $addr;

    public $reusePort;

    public $worker_pid = [];//子进程pid

    public $master_id; //当前主进程id

    protected $watch_fd;//文件监视的句柄

    public function __construct($socket_address)
    {
        $this->addr = $socket_address;
        $this->master_id = posix_getpid();
    }

    public function fork($worknum)
    {

        for ($i = 0; $i < $worknum; $i++) {
            $test=include 'test.php';
            var_dump($test);
            $pid = pcntl_fork();
            if ($pid < 0) {
                exit('创建失败');
            } elseif ($pid > 0) {
                //父进程空间 返回子进程ID
                $this->worker_pid[] = $pid;
            } elseif ($pid == 0) { // 返回0子进程空间
//                sleep(20);
                $this->accept();
                exit();
            }
        }

    }

    public function accept()
    {
        $opts = array(
            'socket' => array(
                'bindlog' => 10240,//成功建立连接的等待个数
            ),
        );
        $context = stream_context_create($opts);
        //开启多端口监听并且实现负载均衡
        stream_context_set_option($context, 'socket', 'so_reuseport', 1); //请求负载均衡分配到不懂进程
        stream_context_set_option($context, 'socket', 'so_reuseaddr', 1); //设置连接重用
        $this->socket = stream_socket_server($this->addr, $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
//        var_dump(posix_getgid());
        //第一个需要监听的时间（服务端socket的事件）一旦监听到事件会触发
        swoole_event_add($this->socket, function ($fd) {
            $clientSocket = stream_socket_accept($fd);
            //出发连接事件回调
            if ($clientSocket && is_callable($this->connect)) {
                call_user_func_array($this->connect, array($fd, '触发连接'));
            }
            //监听客户端可读
            swoole_event_add($clientSocket, function ($fd) {
                $clientData = fread($fd, 65535);
                if (!empty($clientData) && is_callable($this->message)) {
                    call_user_func_array($this->message, array($fd, '触发消息事件'));
                }
                fclose($fd);
            });

        });
//        echo '非柱塞';
    }

    public function watch()
    {
        $this->watch_fd=inotify_init(); //初始化
        $files=get_included_files();
//        var_dump($files);
        foreach ($files as $file){
            inotify_add_watch($this->watch_fd,$file,IN_MODIFY); //监视相关的文件
        }
        //监听
        swoole_event_add($this->watch_fd,function ($fd){
            $events=inotify_read($fd);

            if(!empty($events)){
                echo '修改文件了';
                posix_kill($this->master_id,SIGUSR1);
            }
        });
    }

    public function start()
    {
        $this->watch();

        $this->fork(3);
        $this->monitorWorkers();//监视程序捕获信号 监视worker进程
    }

    public function monitorWorkers()
    {
        //注册信号事件回调 是不会自动执行的
        // reload
        pcntl_signal(SIGUSR1, [$this, 'signalHandler'], false);//重启worker进程信号
        //ctrl+c
        pcntl_signal(SIGINT, [$this, 'signalHandler'], false);//进程信号
        $status = 0;
        while (true) {
            pcntl_signal_dispatch();//当发现信号队列 一旦发现有信号就会触发进程绑定事件回调
            $pid = pcntl_wait($status); //子进程结束的时候触发  结束的子进程信息，阻塞状态
            //如果进程不是正常退出 重启子进程 我想要维持子进程的数量
            $index = array_search($pid, $this->worker_pid);
            if ($pid > 1 && $pid != $this->master_id && $index !== false && !pcntl_wifexited($status)) {
                $this->fork(1);
//                echo '拉起子进程';
//                var_dump($this->worker_pid);
                unset($this->worker_pid[$index]);
            }
            pcntl_signal_dispatch();//进程重启的过程中有信号过来，如果没有调用pcntl_signal_dispatch 原来的信号处理会被打断
//            var_dump($pid, $status);
        }
//        echo "子进程ID=" . $pid . PHP_EOL;
    }

    /**
     * @param $signNum : 信号编号
     */
    public function signalHandler($signNum)
    {
//        var_dump($signNum);
        switch ($signNum) {
            case SIGUSR1:
                echo '收到重启信号';
                $this->reload();
//                swoole_event_del($this->watch_fd);
                break;
            case SIGINT :
                $this->killAll();
                echo "按下ctrl+c,关闭所有进程";
                swoole_event_del($this->watch_fd);
                exit();
                break;
        }
    }

    public function reload()
    {
//        var_dump($this->worker_pid);
        foreach ($this->worker_pid as $index => $pid) {
            posix_kill($pid, SIGKILL);//结束进程
            var_dump("杀掉的子进程", $pid);
            unset($this->worker_pid[$index]);
            $this->fork(1);
        }
    }

    public function killAll()
    {
//        var_dump($this->worker_pid);
        foreach ($this->worker_pid as $index => $pid) {
            posix_kill($pid, SIGKILL);//结束进程
            unset($this->worker_pid[$index]);
        }
    }

}

$worker = new Worker2('tcp://0.0.0.0:9501');

$worker->reusePort = true;

$worker->connect = function ($client, $args) {
//    var_dump($client, $args);
};
$worker->message = function ($client, $args) {
//    var_dump($client, $args);
    $content = "我是peter";
    $http_resonse = "HTTP/1.1 200 OK\r\n";
    $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
    $http_resonse .= "Connection: keep-alive\r\n"; //连接保持
    $http_resonse .= "Server: php socket server\r\n";
    $http_resonse .= "Content-length: " . strlen($content) . "\r\n\r\n";
    $http_resonse .= $content;
    fwrite($client, $http_resonse);
};
$worker->start();